CODv2 3.9 プログラムの起動
CODv2 3.9 プログラムの起動
概要
本節では、ディスク上のファイル中の C プログラムをコンピュータ上で実行可能なプログラムに変換する。
4つのステップ
コンパイラ
アセンブラ
リンカ
ローダ
図3.21 プログラムの翻訳階層
https://gyazo.com/d60c2f3ad55608c9044d8a53715fedce
コンパイラによって(コンパイルされて)、高水準言語プログラム( C プログラム)はアセンブリ言語プログラムに翻訳される
アセンブラによって(アセンブルされて)、機械語のオブジェクト・モジュールに落とされる
リンカによって、複数の機械語のオブジェクト・モジュールをライブラリ・ルーチンと結合、すべての参照関係が解明され実行ファイル(機械語プログラム)に落とされる
ローダによって、機械語プログラム(マシン・コード)をメモリ上の適切な位置に配置して、プロセッサがプログラムを実行できるようにする
ファイルのタイプを識別するために、 UNIX では拡張子を次のように使用する
Cのソース・ ファイルは x.c
アセンブリ ファイルは x.s
オブジェクトファイルは x.o
デフォルトの実行ファイルは a.out
コンパイラ
コンパイラは、高水準言語プログラム(ここでは C のプログラム)を、アセンブリ言語プログラム( Assembly Language Program )に翻訳する
アセンブリ言語プログラムは、マシンが理解できるコードをシンボル(記号)で表現したものである
アセンブラ
疑似命令
アセンブリ言語は上位レベルのソフトウェアへのインターフェースなので、アセンブラは機械語命令を一般化したバリエーションを、もともと備わっている命令であるかのように扱うことができる
こうした命令のバリエーションはハードウェア中に実現されている必要はないが、アセンブリ言語の一環として明示されていると、プログラミングおよび翻訳が単純化される
このような命令を疑似命令( pseudoinstruction )と呼ぶ
疑似命令を使用するとハードウェアで実現されている以上に豊富なアセンブリ言語命令セットを用意できる
そのための犠牲は、アセンブラ専用にレジスタを予約すること
疑似命令用レジスタ $at
move
code:exp3.9.1a.asm
move $t0, $t1 # レジスタ $t1 の値をレジスタ $t0 に移送する
MIPS のハードウェアはレジスタ $zero に必ず値ゼロを保持している
このレジスタ $zero と add 命令を使用すると
あるレジスタの内容を別のレジスタにコピーする move 命令を作成できる
code:exp3.9.1b.asm
add $t0, $zero, $t1 # ゼロ値とレジスタ $t1 の値を合算してレジスタ $t0 に代入する
blt
branch if less than
$t0 < $t1
MIPS のアセンブラは blt (より小さいときに分岐) 命令を slt, bne の2つの命令の組み合わせに変換する
code:exp3.9.2a.asm
blt $t0, $t1, LABEL # $t0 < $t1 のときラベル LABEL へ分岐する
code:exp3.9.2b.asm
slt $at, $t0, $t1 # $t0 < $t1 のとき $at == 1
bne $at, $zero, LABEL # $at == 1 のとき、ラベル LABEL に分岐する
slt ( set on less than )と bne ( branch if not equal ) の組み合わせ
ヒント
ヒント1: slt $at, $t0, $t1 が表現することはなにか?
$t0 < $t1 のとき
$at == 1
それ以外のとき( $t0 >= $t1 のとき)
$at == 0
ヒント2: slt $at, $t1, $t0 が表現することはなにか?
$t0 > $t1 のとき(つまり $t1 < $t0 のとき)
$at == 1
それ以外のとき( $t0 <= $t1 のとき)(つまり$t1 >= $t0 のとき)
$at == 0
bge
branch if greater than or equal
$t0 >= $t1
code:exp3.9.5a.asm
bge $t0, $t1, LABEL # $t0 >= $t1 のときラベル LABEL へ分岐する
code:exp3.9.5b.asm
slt $at, $t0, $t1 # $t0 >= $t1 のとき $at == 0
beq $at, $zero, LABEL # $at == 0 のとき、ラベル LABEL に分岐する
ble
branch if less than or equal
$t0 <= $t1
code:exp3.9.3a.asm
ble $t0, $t1, LABEL # $t0 <= $t1 のときラベル LABEL へ分岐する
code:exp3.9.3b.asm
slt $at, $t1, $t0 # $t1 >= $t0 のとき $at == 0
beq $at, $zero, LABEL # $at == 0 のとき、ラベル LABEL へ分岐
bgt
branch if greater than
$t0 > $t1
code:exp3.9.4a.asm
bgt $t0, $t1, LABEL # $t0 > $t1 のときラベル LABEL へ分岐する
code:exp3.9.3b.asm
slt $at, $t1, $t0 # $t1 < $t0 のとき $at == 1
bne $at, $zero, LABEL # $at == 1 のとき、ラベル LABEL へ分岐
遠くへの分岐
MIPS のアセンブラは遠くへの分岐を、分岐とジャンプに変換する
レジスタ $t0 とレジスタ $t1 が等しいときに、ラベル L1 に分岐する
code:exp3.9.6a.asm
beq $50, $t1, L1
もっと遠くへ分岐可能な命令列で上記命令を置き換える
beq 命令のアドレス・フィールドは 16 ビット
j 命令のアドレス・フィールドは 26ビット
code:exp3.9.6b.asm
bne $t0, $t1, L2
j L1
L2:
レジスタに32ビットの定数をロードする
アセンブラは種々の基数にもとづく数値を受け付ける
2進数、10進数、16進数
アセンブラ本来の役割
アセンブリ言語プログラムをマシン・コード(機械語コード)にアセンブルすること
アセンブラはアセンブリ言語プログラムをオブジェクト・ファイル( object file )に変換する
これは機械語命令およびデータと、命令をメモリ中に適切に配置するための情報とから成る
アセンブリ言語プログラムの各命令のバイナリ版を生成するには、アセンブラはすべてのラベルに対応するアドレスを決定しなければならない
分岐、データ転送の命令中で使用されるラベルを、アセンブラはシンボル表( symbol table )中に記録する
これにはシンボルとアドレスが対で記録される
オブジェクト・ファイルのセクション
UNIX の場合、下記の6セクションで構成される
オブジェクト・ファイル・ヘッダ
該当ファイルの残りの部分のサイズと位置を示す
テキスト・セグメント
機械語コードを保持する
データ・セグメント
プログラムに付随するデータ
静的データ( stable data )
プログラム全体を通じて割り当てられるデータ
動的データ( dynamic data )
プログラムの必要に応じて拡大または縮小しうる
リロケーション情報
プログラムをメモリにロードしたときの、絶対アドレスに依存する命令語およびデータ語
シンボル表
未定義の残りのラベルを保持する
外部参照も
デバッグ情報
プログラムをどのようにコンパイルしたかを詳しく記録する
リンカ
リンク・エディタ( link editor )
リンカ( linker )
リンカの処理の3ステップ
1. コード・モジュールおよびデータ・モジュールをメモリ中に置く
2. データおよび命令のラベルのアドレスを判定する
3. 内部および外部の参照先を解明する
リンカは各オブジェクト・モジュール中のリロケーション情報、シンボル表を使用して、未定義のラベルをすべて解明する
そのような参照は下記で発生する
分岐命令
ジャンプ命令
データ・アドレス
リンカの仕事はエディタに似ている
リンカは古いアドレスを見つけると新しいアドレスで置き換える
これがリンク・エディタの名前の由来である
リンカを使用する意味があるのは、プログラム全体をコンパイルおよびアセンブルし直すよりも、一部のコードだけ手直しする方がはるかに速いから
すべての外部参照先が決定したら、リンカは次に各モジュールの主記憶上のロケーションを決定する
プログラムおよびデータをメモリに割り当てる際の MIPS の規約を以下に示す
図3.22 MIPS におけるプログラムおよびデータのメモリへの割当て
https://gyazo.com/332910c96cf71151b425d3e4ded8462b
スタック・ポインタ $sp が 7fff fffc (16) に初期化されて、上端から下方のデータ・セグメントに向かってスタック領域が伸長する
反対側の 0040 0000 (16) からプログラム・コード(テキスト)が配置される(プログラム・カウンタ PC )
静的データは 1000 0000 (16) から配置される
その次にCのライブラリ・ルーチン malloc によって割当てられる動的データが配置され、スタック領域に向かって上方へ伸長する
グローバル・ポインタの $gp はデータをアクセスしやすいアドレスに設定される。その初期値は 1000 8000 (16) である
したがって $gp からの正および負の16ビット・オフセットを使用して 1000 0000 (16) から 1000 ffff (16) までをアクセスすることができる
各ソース・ファイルは個々にアセンブルされているので、あるモジュールの命令とデータが他のモジュールと相対的にどういう位置に置かれるかはアセンブラではわからなかった(?)
リンカがモジュールを主記憶上に配置するときは、絶対アドレス(レジスタ相対ではないメモリ・アドレス)による参照はすべて実際のロケーションを反映するように調整して再配置( relocate )しなければならない
リンカから出力される実行ファイル( executable file )は、コンピュータ上で走らせることができる
通常このファイルはオブジェクト・ファイルと同じである
ただし未決定の参照先、リロケーション情報、シンボル表、デバッグ情報は含まれていない
ライブラリ・ルーチンなどがそれ
ライブラリ・ルーチンには未決定のアドレスが含まれている。であるので出力はオブジェクト・ファイルとなる
オブジェクト・ファイル → リンカ → 実行ファイル
例題3.9.1 オブジェクト・ファイルのリンク
手続き A 、手続き B の2つのオブジェクト・ファイルをリンクする
リンクの過程で更新されるべきアドレス、シンボルを【】でくくる
それらは、手続き A 、手続き B のアドレスを参照する命令、およびデータ語 X とデータ語 Y を参照する命令である
?わからん?
オブジェクト・ファイルは以下のようになる
table:exp3.9.1a
オブジェクト・ファイル・ヘッダ
名前 手続き A
テキスト・サイズ 100 (16)
データ・サイズ 20 (16)
テキスト・セグメント アドレス 命令
0 lw $a0, 【0】($gp)
4 jal 【0】
… …
データ・セグメント アドレス
【0】 (【X】)
… …
リロケーション情報 アドレス 命令タイプ 依存関係
0 lw 【X】
4 jal 【B】
シンボル表 ラベル アドレス
【X】 ---
【B】 ---
table:exp3.9.1b
オブジェクト・ファイル・ヘッダ
名前 手続き B
テキスト・サイズ 200 (16)
データ・サイズ 30 (16)
テキスト・セグメント アドレス 命令
0 sw $a1,【0】($gp)
4 jal 【0】
… …
データ・セグメント アドレス
【0】 (【Y】)
… …
リロケーション情報 アドレス 命令タイプ 依存関係
0 sw 【Y】
4 jal 【A】
シンボル表 ラベル アドレス
【Y】 ---
【A】 ---
それぞれの手続で見つける必要があるアドレス
手続き A
ロード命令中に記すための X というラベルがついた変数のアドレス
jal 命令中に記すための手続き B のアドレス
手続き B
ストア命令中に記すための Y というラベルがついた変数のアドレス
jal 命令中に記すための手続き A のアドレス
テキスト・セグメントの開始アドレスは 0040 0000 (16)
データ・セグメントの開始アドレスは 1000 0000 (16)
手続き A のテキストとデータはそれぞれのセグメントの最初から配置される
手続き A のテキストの開始アドレスは 0040 0000 (16) 、データの開始アドレスは 1000 0000 (16) となる
手続き A のテキスト・サイズは 100 (16) バイト( 256 バイト)
手続き A のデータ・サイズは 20 (16) バイト ( 32 バイト)
よって、手続き B のテキストの開始アドレスは 0040 0100 (16) 、データの開始アドレスは 1000 0020 (16) となる
リンカは命令のアドレス・フィールドを更新する
リンカは命令タイプ、フィールドを参照して、エディット対象のアドレスのフォーマットを判別する
1. ジャンプ&リンク命令 jal
疑似直接アドレシングを用いる
手続き A の jal 命令(アドレス 0040 0004 (16) )のジャンプ先アドレスは手続き B のアドレス (アドレス 0040 0100 (16) )となる
手続き B の jal 命令(アドレス 0040 0104 (16) )のジャンプ先アドレスは手続き A のアドレス(アドレス 0040 0000 (16) )となる
2. ロード命令 lw 、ストア命令 sw
ベース・レジスタに対する相対アドレシングを用いる
図3.22 にはグローバル・ポインタ $gp は 1000 8000 (16) に初期化されることが示されている
手続き A の変数、語 X のアドレス(アドレス 1000 0000 (16) )を得るには
テキスト・フィールド 0040 0000 (16) にある lw 命令のアドレスフィールドに 8000 (16) を入れる
16ビットの2の補数による算術演算の方法は第4章で示すとのこと
手続き B の変数、語 Y のアドレス(アドレス 1000 0020 (16) )を得るには
テキスト・フィールド 0040 0100 (16) にある sw 命令のアドレスフィールドに 8020 (16) を入れる
リンカーがリンクした実行ファイルは以下のようになる
table:exp3.9.1c
実行・ファイル・ヘッダ
テキスト・サイズ 300 (16)
データ・サイズ 50 (16)
テキスト・セグメント アドレス 命令
0040 0000 (16) lw $a0, 8000 (16)($gp)
0040 0004 (16) jal 0040 0100 (16)
… …
0040 0100 (16) sw $a1, 8020 (16)($gp)
0040 0104 (16) jal 0040 0000 (16)
データ・セグメント アドレス
1000 0000 (16) (X)
… …
1000 0020 (16) (Y)
ローダ
実行ファイルはディスク上にある。オペレーティング・システムがメモリに読み込んで起動する
(付録A 「A.4 ロード」より)
UNIX システムの場合
1. 実行ファイルのヘッダを読んで、テキスト・セグメントとデータ・セグメントの大きさを判定する
2. プログラム用のアドレス空間を(メモリ上に)作成する。このアドレス空間はテキスト・セグメントとデータ・セグメントに加えて、スタック・セグメントを保持するのに十分な大きさのものである
3. 実行ファイルから命令とデータを(メモリ上の)新しいアドレス空間にコピーする
https://gyazo.com/332910c96cf71151b425d3e4ded8462b
4. メイン・プログラムに渡される引数(パラメータ)があれば、これをスタック上にコピーする
https://gyazo.com/04ae84c49ce0baac1952340da27340a8
5. マシンのレジスタを初期化する。スタック・ポインタに最初の空きロケーションを設定する
それ意外のレジスタは、クリアされる
6. 開始ルーチン( start-up routine )にジャンプする。開始ルーチンはスタックからプログラムのパラメータをレジスタにコピーして、プログラムのメイン・ルーチンを呼出す。
7. メイン・ルーチンから制御が戻ると、開始ルーチンは終了システムコール( exit system call )を用いてプログラムを終了させる